﻿using System;
using System.Text;

namespace Douya.Json
{
    public class JsonParser
    {
        public JsonObject Parse(string json)
        {
            ObjectBuilder builder = new ObjectBuilder();

            if (json.StartsWith("{"))
            {
                ParseObject(builder, json.ToCharArray(), 1);
            }

            return builder.Build();
        }

        int ParseObject(ObjectBuilder builder, char[] buffer, int offset)
        {
            int index = offset;
            int limit = buffer.Length;

            while (index < buffer.Length)
            {
                index += SkipWhiteSpace(buffer, index);
                if (index >= limit)
                {
                    break;
                }

                if (buffer[index] != '\"') //begin of name
                {
                    break;
                }
                ++index;

                // name
                StringBuilder name = new StringBuilder();
                index += ParseString(name, buffer, index);

                index += SkipWhiteSpace(buffer, index);
                if (index >= limit)
                {
                    break;
                }

                if (buffer[index] != ':') //begin of value
                {
                    break;
                }
                ++index;

                index += SkipWhiteSpace(buffer, index);
                if (index >= limit)
                {
                    break;
                }

                // value
                IJsonValue value;
                index += ParseValue(buffer, index, out value);

                // Add item of object.
                if (name.Length > 0 && value != null)
                {
                    builder.Add(name.ToString(), value);
                }

                index += SkipWhiteSpace(buffer, index);
                if (index >= limit)
                {
                    break;
                }

                if (buffer[index] == ',')
                {
                    ++index;
                    continue;
                }
                else if (buffer[index] == '}') // end of object
                {
                    ++index;
                    break;
                }
                else
                {
                    break;
                }
            }

            return index - offset;
        }

        int ParseArray(ArrayBuilder builder, char[] buffer, int offset)
        {
            int index = offset;
            int limit = buffer.Length;

            while (index < buffer.Length)
            {
                index += SkipWhiteSpace(buffer, index);
                if (index >= limit)
                {
                    break;
                }

                // value
                IJsonValue value;
                index += ParseValue(buffer, index, out value);

                // Add item of array.
                if (value != null)
                {
                    builder.Add(value);
                }

                index += SkipWhiteSpace(buffer, index);
                if (index >= limit)
                {
                    break;
                }

                if (buffer[index] == ',')
                {
                    ++index;
                    continue;
                }
                else if (buffer[index] == ']') // end of array
                {
                    ++index;
                    break;
                }
                else
                {
                    break;
                }
            }

            return index - offset;
        }

        int ParseString(StringBuilder builder, char[] buffer, int offset)
        {
            int index = offset;

            while (index < buffer.Length)
            {
                char chr = buffer[index];
                if (chr == '\"')
                {
                    ++index;
                    break;
                }
                else if (chr == '\\' && index < buffer.Length - 1)
                {
                    chr = buffer[index + 1];
                    if (chr == '\"')
                    {
                        builder.Append('\"');
                    }
                    else if (chr == '\\')
                    {
                        builder.Append('\\');
                    }
                    else if (chr == '/')
                    {
                        builder.Append('/');
                    }
                    else if (chr == 'b')
                    {
                        builder.Append('\b');
                    }
                    else if (chr == 'f')
                    {
                        builder.Append('\f');
                    }
                    else if (chr == 'n')
                    {
                        builder.Append('\n');
                    }
                    else if (chr == 'r')
                    {
                        builder.Append('\r');
                    }
                    else if (chr == 't')
                    {
                        builder.Append('\t');
                    }
                    else if (chr == 'u')
                    {
                        builder.Append(new string(buffer, index, 6));
                        index += 4; //\u1234
                    }
                    else
                    {
                        builder.Append('\\');
                        builder.Append(chr);
                    }
                    index += 2;
                }
                else
                {
                    builder.Append(chr);
                    ++index;
                }
            }

            return index - offset;
        }

        int ParseValue(char[] buffer, int offset, out IJsonValue value)
        {
            int index = offset;

            char chr = buffer[index];
            if (chr == '{')
            {
                ++index;
                ObjectBuilder builder = new ObjectBuilder();
                index += ParseObject(builder, buffer, index);
                value = builder.Build();
            }
            else if (chr == '[')
            {
                ++index;
                ArrayBuilder builder = new ArrayBuilder();
                index += ParseArray(builder, buffer, index);
                value = builder.Build();
            }
            else if (chr == '\"')
            {
                ++index;
                StringBuilder builder = new StringBuilder();
                index += ParseString(builder, buffer, index);
                value = new JsonString(builder.ToString());
            }
            else
            {
                StringBuilder builder = new StringBuilder();
                index += ParseValue(builder, buffer, index);
                value = ParseValue(builder.ToString());
            }

            return index - offset;
        }

        int ParseValue(StringBuilder builder, char[] buffer, int offset)
        {
            int index = offset;

            while (index < buffer.Length)
            {
                char chr = buffer[index];
                if (chr == ',' || chr == '}' || chr == ']')
                {
                    break;
                }
                else if (chr == ' ' || chr == '\n' || chr == '\r' || chr == '\t')
                {
                    break;
                }
                else
                {
                    builder.Append(chr);
                    ++index;
                }
            }

            return index - offset;
        }

        IJsonValue ParseValue(string value)
        {
            IJsonValue result;
            if (value == "true")
            {
                result = new JsonBoolean(true);
            }
            else if (value == "false")
            {
                result = new JsonBoolean(false);
            }
            else if (value == "null")
            {
                result = new JsonNull();
            }
            else if (IsInteger(value))
            {
                result = new JsonInteger(int.Parse(value));
            }
            else
            {
                result = new JsonString(value);
            }
            return result;
        }

        int SkipWhiteSpace(char[] buffer, int offset)
        {
            int index = offset;

            while (index < buffer.Length)
            {
                char chr = buffer[index];
                if (chr != ' ' && chr != '\n' && chr != '\r' && chr != '\t')
                {
                    break;
                }
                ++index;
            }

            return index - offset;
        }

        bool IsInteger(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return false;
            }

            char[] chars = value.ToCharArray();

            // first character
            if (!char.IsDigit(chars[0]) && chars[0] != '-')
            {
                return false;
            }

            for (int i = 1; i < chars.Length; ++i)
            {
                if (!char.IsDigit(chars[i]))
                {
                    return false;
                }
            }

            return true;
        }
    }
}
